package distributedlock.redis;

import org.apache.log4j.Logger;

import util.Configure;
import util.RedisUtil;

/**
 * 简易的分布式锁（如果Redis本身是比较大的集群可以考虑使用Redisson）
 * 推荐这种写法
 *
 */
public class RedisLock2 {

	private Logger log = Logger.getLogger(RedisLock2.class);

	public RedisLock2() {

	}
	
	public RedisLock2(String suffix) {
		this.key = this.key + ":" + suffix;
	}

	private static final Configure INS = Configure.getInstans();

	/**
	 *  默认超时时间（毫秒） 
	 *  超时会抛弃当前锁里的所有线程
	 *  必要时需要补偿机制
	 */
	public static final long DEFAULT_TIMEOUT = Long.parseLong(INS.getValue("DEFAULT_TIMEOUT"));
	/**
	 *  锁的超时时间（毫秒），过期删除 
	 */
	public static final long LOCK_TIMEOUT = Long.parseLong(INS.getValue("LOCK_TIMEOUT"));
	// 锁状态标志
	private boolean locked = false;
	private String key = "LOCK";
	private RedisUtil redisUtil = RedisUtil.getInstans();
	

	public boolean lock() throws InterruptedException {
		long timeout = DEFAULT_TIMEOUT;
		while (timeout >= 0) {
			long expires = System.currentTimeMillis() + LOCK_TIMEOUT + 1;
			String expiresStr = String.valueOf(expires); // 锁到期时间
			System.out.println("expiresStr : " + expiresStr);
			log.info("RedisLock--lock expiresStr : " + expiresStr);

			if (redisUtil.setnx(key, expiresStr) == 1) {
				// lock acquired
				locked = true;
				log.info("RedisLock--lock GET Lock key=" + key);
				return true;
			}

			String currentValueStr = redisUtil.get(key); // redis里的时间
			System.out.println("currentValueStr : " + currentValueStr);
			log.info("RedisLock--lock currentValueStr : " + currentValueStr);

			// 判断是否为空，不为空的情况下，如果被其他线程设置了值，则第二个条件判断是过不去的
			if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
				// lock is expired

				// 获取上一个锁到期时间，并设置现在的锁到期时间，只有一个线程才能获取上一个线上的设置时间，因为jedis.getSet是同步的
				String oldValueStr = redisUtil.getGet(key, expiresStr);
				System.out.println("oldValueStr : " + oldValueStr);
				log.info("RedisLock--lock oldValueStr : " + oldValueStr);

				// 如过这个时候，多个线程恰好都到了这里，但是只有一个线程的设置值和当前值相同，他才有权利获取锁
				if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
					// lock acquired

					locked = true;
					log.info("RedisLock--lock GET Lock By oldValueStr.equals(currentValueStr) : oldValueStr="+ oldValueStr + " currentValueStr=" + currentValueStr + " key=" + key);
					
					return true;
				}
			}
			timeout = timeout - 100;
			Thread.sleep(100);
		}
		return false;
	}

	public void unlock() {
		System.out.println("unlock : " + locked);
		log.info("RedisLock--unlock locked=" + locked);
		if (locked) {
			redisUtil.del(key);
			System.out.println("unlock done");
			log.info("RedisLock--unlock : Del Lock key=" + key);
		}

	}
}